/*
 *  levelUpGame - Player.cpp
 *  Copyright 2011 W. Reckman. All rights reserved.
 */
#include "LevelUpApp.h"
#include "Player.h"

const float Player::MAX_BITE_ANGLE = 25.0f;
const float Player::BLINK_DELAY_SECONDS = 0.4f;
const float Player::BLINK_MIN_OPACITY = 0.5f;

//--------------------------------------------------------------
//NOTE: drawRotated is being set to something silly in order to immediately trigger a swap in update()
Player::Player( const LevelUpApp& appRef_ )
: GridObject( appRef_ ), appRef( appRef_ ), game( appRef.getGame() ), topImage( 0 ),
  bottomImage( 0 ), drawMirrored( false ), drawRotated( 1000 ),
  biteSpeed( game.getBaseBiteSpeed() ), lastGameTime( game.getTime() ),
  biteAngleIncreasing( true ), biteAngle( 0.0f ), lastBlinkTime( -1 ), drawAlpha( 1.0f )
{}

//--------------------------------------------------------------
Player::~Player() {
	delete topImage; topImage = 0;
	delete bottomImage; bottomImage = 0;
}


//--------------------------------------------------------------
//FIXME: ownership of top/bottomImage pointers
void Player::setup( const GameModel::PlayerInfo& info_ ) {
	bool rv;
	
	info = info_;
	
	topImage = new ofImage();
	rv = topImage->loadImage( *info.topImagePath );
	
	if ( ! rv ) {
		ofLog( OF_LOG_ERROR, "(Player.cpp) could not load top image ('%s')", info.topImagePath->c_str() );
	}
	
	bottomImage = new ofImage();
	rv = bottomImage->loadImage( *info.bottomImagePath );
	
	if ( ! rv ) {
		ofLog( OF_LOG_ERROR, "(Player.cpp) could not load top image ('%s')", info.bottomImagePath->c_str() );
	}
	
	if ( info.cultureTgt + info.industryTgt != 100 ) {
		ofLog( OF_LOG_WARNING, "(Player.cpp) target balance values are incorrect ( %i + %i != 100)", info.cultureTgt, info.industryTgt );
	}
	
	setSize( bottomImage->getWidth(), topImage->getHeight() + bottomImage->getHeight() );
	targetBalance.setBalanceCulture( info.cultureTgt );
	
	setCollisionMode( CM_CIRCLE );
	
	ofAddListener( unfreezeTimer.TIMER_REACHED, this, &Player::onUnfreezeEvent );
	ofAddListener( corporealizeTimer.TIMER_REACHED, this, &Player::onCorporealizeEvent );
}

//--------------------------------------------------------------
void Player::update() {
	float elapsed = game.getTime() - lastGameTime;
	
	if ( biteAngleIncreasing ) {
		biteAngle += biteSpeed * elapsed;
		if ( biteAngle >= MAX_BITE_ANGLE ) {
			biteAngle = MAX_BITE_ANGLE;
			biteAngleIncreasing = false;
		}
	} else {
		biteAngle -= biteSpeed * elapsed;
		if ( biteAngle <= 0.0f ) {
			biteAngle = 0.0f;
			biteAngleIncreasing = true;
		}
	}
	
	if ( game.getState() != GS_PRECOUNT && game.getState() != GS_PLAYING ) {
		lastGameTime = game.getTime();
		return;
	}
	
	
	/* NOTE: Code from this point on is movement code, only executed in game states GS_PRECOUNT and GS_PLAYING */
	
	DIRECTION origDir = currentDir;
	ofPoint newPos;
	bool wantsToMove = false;
	
	if ( ! blocked ) {
		
		
		
		
		newPos = getPosition();
		wantsToMove = calculateNewPosForDirection( intendedDir, elapsed, newPos );
		
		if ( wantsToMove ) {
			if ( tryMovingTo( newPos, intendedDir ) ) {
				currentDir = intendedDir;
			} else {
				newPos = getPosition();
				wantsToMove = calculateNewPosForDirection( currentDir, elapsed, newPos );
				
				if ( wantsToMove ) {
					if ( tryMovingTo( newPos, currentDir ) ) {
					} else {
						//we can't move at the moment...but just keep trying each update
					}
				}
			}
		}
		
		
		
		
		
		
		//		if ( intendedDir != GameModel::STOP ) {
		//			float slby = game.getStageLaneBottomY();
		//			const GameModel::vec_int& cl = game.getColumnLanes();
		//			const GameModel::vec_int& rl = game.getRowLanes();
		//			//float moveDist = movementSpeed * elapsed;
		//			int moveDist = movementSpeed * elapsed;
		//			
		//ofLog( OF_LOG_WARNING, "Pl %i MOV_START", getId() );
		//			while ( moveDist > 0.0f ) {
		//				GridPoint<int> gp( getPosition() );
		//				game.fillInLaneIndices( gp, 3, true );
		//ofLog( OF_LOG_WARNING, "Pl %i @ (%i, %i)(%i, %i)", getId(), gp.colIdx, gp.rowIdx, gp.x, gp.y );
		//				
		//				bool moved = false;
		//				//if ( intendedDir == GameModel::LEFT && gp.rowIdx > 0 && gp.x > cl[0] ) {
		//				if ( intendedDir == GameModel::LEFT && gp.x > cl[0] ) {
		//					moved = true;
		//					if ( moveDist < gp.x - cl[0] ) { gp.x -= moveDist; moveDist = 0.0f; }
		//					else { moveDist -= gp.x - cl[0]; gp.x = cl[0]; }
		//					
		//				}
		//				//if ( intendedDir == GameModel::RIGHT && gp.rowIdx > 0 && gp.x < cl[cl.size() - 1] ) {
		//				if ( intendedDir == GameModel::RIGHT && gp.x < cl[cl.size() - 1] ) {
		//					moved = true;
		//					if ( moveDist < cl[cl.size() - 1] - gp.x ) { gp.x += moveDist; moveDist = 0.0f; }
		//					else { moveDist -= cl[cl.size() - 1] - gp.x; gp.x = cl[cl.size() - 1]; }
		//					
		//				}
		//				if ( intendedDir == GameModel::UP && gp.colIdx > 0 && gp.y > rl[0] ) {
		//					moved = true;
		//					if ( moveDist < gp.y - rl[0] ) { gp.y -= moveDist; moveDist = 0.0f; }
		//					else { moveDist -= gp.y - rl[0]; gp.y = rl[0]; }
		//					
		//				}
		//				if ( intendedDir == GameModel::DOWN && gp.colIdx > 0 && gp.y < slby ) {
		//					moved = true;
		//					if ( moveDist < slby - gp.y ) { gp.y += moveDist; moveDist = 0.0f; }
		//					else { moveDist -= slby - gp.y; gp.y = slby; }
		//				}
		//				
		//				if ( moved ) {
		//					currentDir = intendedDir;
		//				} else {
		//					if ( currentDir == GameModel::LEFT && gp.rowIdx > 0 && gp.x > cl[0] ) {
		//						moved = true;
		//						if ( moveDist < gp.x - cl[0] ) { gp.x -= moveDist; moveDist = 0.0f; }
		//						else { moveDist -= gp.x - cl[0]; gp.x = cl[0]; }
		//						
		//					}
		//					if ( currentDir == GameModel::RIGHT && gp.rowIdx > 0 && gp.x < cl[cl.size() - 1] ) {
		//						moved = true;
		//						if ( moveDist < cl[cl.size() - 1] - gp.x ) { gp.x += moveDist; moveDist = 0.0f; }
		//						else { moveDist -= cl[cl.size() - 1] - gp.x; gp.x = cl[cl.size() - 1]; }
		//						
		//					}
		//					if ( currentDir == GameModel::UP && gp.colIdx > 0 && gp.y > rl[0] ) {
		//						moved = true;
		//						if ( moveDist < gp.y - rl[0] ) { gp.y -= moveDist; moveDist = 0.0f; }
		//						else { moveDist -= gp.y - rl[0]; gp.y = rl[0]; }
		//						
		//					}
		//					if ( currentDir == GameModel::DOWN && gp.colIdx > 0 && gp.y < slby ) {
		//						moved = true;
		//						if ( moveDist < slby - gp.y ) { gp.y += moveDist; moveDist = 0.0f; }
		//						else { moveDist -= slby - gp.y; gp.y = slby; }
		//					}
		//				}
		//				
		//				if ( moved ) setPosition( gp.x, gp.y );
		//				else moveDist = 0.0f; //we can't move, set moveDist to 0 in order to break the while loop
		//			}
		//ofLog( OF_LOG_WARNING, "Pl %i MOV_END", getId() );
		//		} else {
		//			currentDir = GameModel::STOP;
		//		}
		
		
		
		
		
	}
	
	if ( ! isCollidable() ) {
		if ( lastBlinkTime < 0 ) {
			lastBlinkTime = game.getTime();
			drawAlpha = 1.0f;
		} else {
			float elapsed = game.getTime() - lastBlinkTime;
			if ( elapsed < BLINK_DELAY_SECONDS * 2 ) {
				if ( elapsed < BLINK_DELAY_SECONDS )
					drawAlpha = ofMap( elapsed, 0.0f, BLINK_DELAY_SECONDS, 1.0f, BLINK_MIN_OPACITY, true );
				else
					drawAlpha = ofMap( elapsed, BLINK_DELAY_SECONDS, BLINK_DELAY_SECONDS * 2, BLINK_MIN_OPACITY, 1.0f, true );
			} else {
				lastBlinkTime = game.getTime();
				drawAlpha = 1.0f;
			}
		}
	} else {
		lastBlinkTime = -1;
		drawAlpha = 1.0f;
	}
	
	if ( currentDir == D_LEFT ) drawMirrored = true;
	else if ( currentDir == D_RIGHT ) drawMirrored = false;
	
#ifdef ROTATE_PLAYERS
	int prevRotate = fabsf( drawRotated );
	if ( getDirection() == D_UP ) drawRotated = -90;
	else if ( getDirection() == D_DOWN ) drawRotated = 90;
	else drawRotated = 0;
	
	//NOTE: convert bbox between portrait and landscape when rotating
	if ( prevRotate != (int)fabsf( drawRotated ) ) {
		ofRectangle bbox = getBBox();
		setSize( bbox.height, bbox.width );
	}
#endif
	
	if ( origDir != currentDir && origDir != D_STOP && currentDir != D_STOP ) {
		//NOTE: beware the const cast!
		GameController& gameController = const_cast<GameController&>( appRef.getGameController() );
		gameController.argsGameEvent.eventType = EventArgsGame::GE_PLAYER;
		gameController.argsGameEvent.eventSubtype = EventArgsGame::GES_PLAYER_TURN;
		gameController.argsGameEvent.objType = EventArgsGame::GOT_PLAYER;
		gameController.argsGameEvent.obj.player = this;
		ofNotifyEvent( gameController.gameEvent, gameController.argsGameEvent, this );
	}
	
	lastGameTime = game.getTime();
}

//--------------------------------------------------------------
void Player::drawAtPos( int x_, int y_, float animPos_, float scale_ ) const {
	//NOTE: FIXME: dirty hack to have images keep rotating
	draw( x_, y_ - game.getPlayerYSubtract(), drawRotated + 90 * animPos_, scale_ );
}

//--------------------------------------------------------------
void Player::draw( bool drawAsWinner_, float animPos_ ) const {
	const ofPoint& pos = getPosition();
	if ( ! drawAsWinner_ ) {
		draw( pos.x, pos.y - game.getPlayerYSubtract(), drawRotated, 1.0f );
	} else {
		const ViewController& vCtl = appRef.getViewController();
		ofPoint center( vCtl.getCanvasWidth() / 2, vCtl.getCanvasHeight() / 2 );
		ofPoint dp = pos + ( center - pos ) * animPos_;
		draw( dp.x, dp.y, drawRotated * ( 1.0f - animPos_ ), 1.0f + ( game.getEndAnimScaleUp() - 1 ) * animPos_ );
	}
}


//--------------------------------------------------------------
int Player::getId() const { return info.id; }
const ofColor& Player::getColor() const { return info.color; }
int Player::getStartingLane() const { return startingLane; }
const BalanceMeter& Player::getCurrentBalance() const { return currentBalance; }
const BalanceMeter& Player::getTargetBalance() const { return targetBalance; }
const GameModel::PlayerInfo& Player::getInfo() const { return info; }


//--------------------------------------------------------------
void Player::setStartingLane( int startingLane_ ) {
	startingLane = startingLane_;
	targetBalance.setPosition( game.getColumnLanes()[startingLane], game.getStageLaneBottomY() );
	currentBalance.setPosition( game.getColumnLanes()[startingLane], game.getStageLaneBottomY() - BalanceMeter::METER_HEIGHT - 5 );
}

//--------------------------------------------------------------
void Player::addBalance( int val_, BALANCE_KIND kind_ ) {
	switch( kind_ ) {
		case BK_CULTURE:
			currentBalance.addBalanceCulture( val_ );
			break;
		case BK_INDUSTRY:
			currentBalance.addBalanceIndustry( val_ );
			break;
	}
}

//--------------------------------------------------------------
void Player::resetPosition() {
	setPosition( ofPoint( game.getColumnLanes()[startingLane], game.getStageLaneBottomY() ) );
}

//--------------------------------------------------------------
void Player::incrementGoodPickupCount() { info.goodPickups++; }
void Player::incrementBadPickupCount() { info.badPickups++; }

//--------------------------------------------------------------
void Player::startFreezeTimeout( float seconds_ ) {
	unfreezeTimer.setup( seconds_ * 1000, false );
	unfreezeTimer.startTimer();
}

//--------------------------------------------------------------
void Player::startNonCorporealTimeout( float seconds_ ) {
	corporealizeTimer.setup( seconds_ * 1000, false );
	corporealizeTimer.startTimer();
}


/*******************
 * EVENT CALLBACKS *
 *******************/

//--------------------------------------------------------------
void Player::onUnfreezeEvent( ofEventArgs& ev_ ) {
	GameController& gameController = const_cast<GameController&>( appRef.getGameController() );
	gameController.argsGameEvent.eventType = EventArgsGame::GE_PLAYER;
	gameController.argsGameEvent.eventSubtype = EventArgsGame::GES_PLAYER_UNFREEZE;
	gameController.argsGameEvent.objType = EventArgsGame::GOT_PLAYER;
	gameController.argsGameEvent.obj.player = this;
	ofNotifyEvent( gameController.gameEvent, gameController.argsGameEvent, this );
}

//--------------------------------------------------------------
void Player::onCorporealizeEvent( ofEventArgs& ev_ ) {
	GameController& gameController = const_cast<GameController&>( appRef.getGameController() );
	gameController.argsGameEvent.eventType = EventArgsGame::GE_PLAYER;
	gameController.argsGameEvent.eventSubtype = EventArgsGame::GES_PLAYER_CORPOREALIZE;
	gameController.argsGameEvent.objType = EventArgsGame::GOT_PLAYER;
	gameController.argsGameEvent.obj.player = this;
	ofNotifyEvent( gameController.gameEvent, gameController.argsGameEvent, this );
}


/*********************
 * PRIVATE FUNCTIONS *
 *********************/

//--------------------------------------------------------------
void Player::draw( int x_, int y_, float rotation_, float scale_ ) const {
	const ofPoint& drawDims = game.getPlayerDrawSize();
	float drawW = drawDims.x;
	float drawH = drawDims.y;
	float topDrawH = drawDims.y * GameModel::PLAYER_TOP_TO_TOTAL_RATIO;
	float bottomDrawH = drawDims.y - drawDims.y * GameModel::PLAYER_TOP_TO_TOTAL_RATIO;
	const ofPoint& pos = getPosition();
	
	ofPushStyle();
	ofPushMatrix();
	
	if ( drawMirrored ) {
		glScalef( -1, 1, 1 );
		ofTranslate( -x_, y_, 0 );
	} else {
		ofTranslate( x_, y_, 0 );
	}
	glScalef( scale_, scale_, scale_ );
	ofRotateZ( rotation_ );
	
#ifdef DRAW_PLAYER_CIRCLES
		ofSetColor( info.color.r, info.color.g, info.color.b, 255 * drawAlpha * 0.5f );
		ofCircle( 0, 0, drawW / 2 );
#endif
	
	ofSetColor( 255, 255, 255, 255 * drawAlpha );
	
	topImage->draw( -drawW / 2, -drawH / 2, drawW, topDrawH );
	
	ofTranslate( -drawW / 2, drawH / 2 - bottomDrawH, 0 );
	ofRotateZ( biteAngle );
	bottomImage->draw( 0, 0, drawW, bottomDrawH );
	
	ofPopMatrix();
	
	if ( appRef.debugDrawingEnabled ) {
		GridObject::draw();
	}
	
	ofPopStyle();
}
